//==========================================================================
// Copyright (c) 2000-2009,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "CGrafixEngine.h"
#include "_CGrafixEngine.cpp"

VinciLock  g_lock;
Int32 g_screenSize = 0;

Matrix g_transformMatrix[4] = {
    {1, 0, 0, 1, 0, 0},
    {0, 1, -1, 0, 0, 0},
    {-1, 0, 0, -1, 0, 0},
    {0, -1, 1, 0, 0, 0},
};

Matrix g_RenderMatrix[4] = {
    {1, 0, 0, 1, 0, 0},
    {0, -1, 1, 0, 0, 0},
    {-1, 0, 0, -1, 0, 0},
    {0, 1, -1, 0, 0, 0},
};

#ifdef _DEBUG
STATIC Int32    m_gfxengine = 0;
#endif

ECode CGrafixEngine::constructor()
{
    // be sure that the layer is the singleton object
    //
    assert(0 == m_gfxengine++);

    m_elementCount = 0;

    for (Int32 i = 0; i < MAX_LAYER; i++) {
        m_collection[i] = NULL;
    }

    m_stack = NULL;
    m_stackRef = 0;
    m_orientation = ScreenOrientation_Up;

    return NOERROR;
}

CGrafixEngine::~CGrafixEngine()
{
    assert(1 == m_gfxengine);

#ifdef _DEBUG
    m_gfxengine = 0;
#endif

    g_lock.Lock();

    // The GfxEngine does not hold the layer's reference, but the layer hold
    // the GfxEngine's reference.
    //
    for (Int32 i = 0; i < m_elementCount; i++) {
        if (m_collection[i]) {
            m_collection[i] = NULL;
        }
    }

    m_elementCount  = 0;

    if (m_stack) {
        delete m_stack;
        m_stack = NULL;
    }

    while (!m_eventBufferHeader.IsEmpty()) {
        CGrafixEventBuffer * buffer = (CGrafixEventBuffer *)m_eventBufferHeader.Next();
        buffer->m_stack = NULL;
        buffer->Detach();
    }

    GFX_EXPRESSION(DEBUG_DESTRUCTOR, kprintf("~CGrafixEngine()\n"));

    g_lock.Unlock();
}

ECode CGrafixEngine::AcquireGrafixLayer(
    /* [in] */ Int32 layerId,
    /* [out] */ IGrafixLayer ** ppLayer)
{
    ECode ec = NOERROR;

    if (!ppLayer || layerId < 0) {
        GFX_EXPRESSION(ERRORLEVEL,
            kprintf("Invalid argument\n"));

        return E_INVALID_ARGUMENT;
    }

    *ppLayer = NULL;

    CGrafixLayer* newLayer;

    g_lock.Lock();

    for (int i = 0; i < m_elementCount; i++) {
        if (layerId == m_collection[i]->m_info.layer) {
            m_collection[i]->AddRef();
            *ppLayer = m_collection[i];

            goto Exit;
        }
    }

    newLayer = new CGrafixLayer(this, layerId);
    if (!newLayer) {
        GFX_EXPRESSION(ERRORLEVEL,
            kprintf("Not enough memory\n"));
        ec = E_OUT_OF_MEMORY;

        goto Exit;
    }

    // The grafix engine doesn't hold the layer's reference, the AddRef() just
    // for user.
    //
    newLayer->AddRef();
    *ppLayer = newLayer;
    m_collection[m_elementCount++] = newLayer;

Exit:
    g_lock.Unlock();

    return ec;
}

ECode CGrafixEngine::CreateGrafixObject(
    /* [in] */ WString name,
    /* [in] */ const struct GrafixObjectParams & params,
    /* [out] */ IGrafixObject ** ppGrafixObject)
{
    // check the argument
    //
    if (!ppGrafixObject) {
        GFX_ERROR_MESSAGE("Invalid argument\n");

        return E_INVALID_ARGUMENT;
    }

    if ((params.objectArea.width <= 0) || (params.objectArea.height <= 0)
            || (params.objectArea.width * params.objectArea.height
                > MAX_WINDOW_SIZE)) {
        GFX_ERROR_MESSAGE("Invalid argument or the size is larger than max\n");

        return E_INVALID_ARGUMENT;
    }

    GrafixSurfaceParams surfaceParams = {
                params.attribute,
                {RECTANGLE_VALS(&params.objectArea)},
                {-1, -1, -1, -1},
                PixelFormat_RGB24};

    *ppGrafixObject = NULL;

    g_lock.Lock();

    CGrafixObject* object = NULL;
    CGrafixSurface* surface = NULL;
    ECode ec = NOERROR;

    if (!m_stack) {
        GFX_ERROR_MESSAGE("The grafixobjectstack doesn't initialized\n");
        ec = E_STACK_NOT_INITIALIZE;

        goto Exit;
    }

    if (params.attribute & GrafixAttribute_AlphaPixel) {
        surfaceParams.pixelFormat = PixelFormat_ARGB;
    }

    // create grafixobject's surface
    //
    surface = new CGrafixSurface;
    if (!surface) {
        GFX_EXPRESSION(ERRORLEVEL,
            kprintf("Create surface not enough memory\n"));
        ec = E_OUT_OF_MEMORY;

        goto Exit;
    }

    object = new CGrafixObject;
    if (!object) {
        GFX_EXPRESSION(ERRORLEVEL,
            kprintf("Create grafixobject that is not enough memory\n"));
        delete surface;
        ec = E_OUT_OF_MEMORY;

        goto Exit;
    }

    object->AddRef();

    ec = object->Initialize(m_stack,
                            surface,
                            name,
                            params);
    if (GFX_FAILED(ec)) {
        goto Exit;
    }

    ec = surface->Initialize(surfaceParams, object);
    if (GFX_FAILED(ec)) {
        goto Exit;
    }

    ec = m_stack->AddWindow(object);
    if (GFX_FAILED(ec)) {
        goto Exit;
    }

    if (object->m_config.option & GrafixAttribute_StickyBuffer) {
        g_bufferCache.GetBufferNode(
                    surface,
                    surfaceParams.surfaceArea.width,
                    surfaceParams.surfaceArea.height,
                    surfaceParams.attribute,
                    surfaceParams.pixelFormat,
                    &surface->m_cacheBuffer);
    }

    *ppGrafixObject = IGrafixObject::Probe((_IInterface*)object);
    if (*ppGrafixObject) {
        (*ppGrafixObject)->AddRef();
    }
    else {
        ec = E_NO_INTERFACE;
    }

Exit:
    g_lock.Unlock();

    if (object) {
        object->Release();
    }

    return ec;
}

ECode CGrafixEngine::CreateGrafixSurface(
    /* [in] */ const struct GrafixSurfaceParams & params,
    /* [out] */ IGrafixSurface ** ppGrafixSurface)
{
    // check argument
    //
    if (!ppGrafixSurface) {
        GFX_EXPRESSION(ERRORLEVEL,
            kprintf("The surface is NULL\n"));

        return E_INVALID_ARGUMENT;
    }

    if ((params.surfaceArea.width <= 0) || (params.surfaceArea.height <= 0)
            || (params.surfaceArea.width * params.surfaceArea.height
                > MAX_WINDOW_SIZE)) {
        GFX_ERROR_MESSAGE("Invalid argument or the size is larger than max\n");

        return E_INVALID_ARGUMENT;
    }

    *ppGrafixSurface = NULL;

    if (PixelFormat_Unknown != params.pixelFormat
            && PixelFormat_ARGB != params.pixelFormat
            && PixelFormat_RGB24 != params.pixelFormat
            && PixelFormat_A8 != params.pixelFormat) {
        GFX_EXPRESSION(ERRORLEVEL,
            kprintf("The pixel format doesn't support 0x%08x\n",
            params.pixelFormat));

        return E_INVALID_ARGUMENT;
    }

    if (!RectangleValid((Rectangle *)&params.surfaceArea)) {
        GFX_EXPRESSION(ERRORLEVEL,
            kprintf("The descripton is invalid\n"));

        return E_INVALID_ARGUMENT;
    }

    if (((PixelFormat_RGB24 == params.pixelFormat)
            || (PixelFormat_A8 == params.pixelFormat))
                && (params.attribute & GrafixAttribute_AlphaPixel)) {
        GFX_EXPRESSION(ERRORLEVEL,
            kprintf("The RGB24 does not have the AlphaPixel attribute\n"));

        return E_INVALID_ARGUMENT;
    }

    GrafixSurfaceParams surfaceParams = params;
    surfaceParams.attribute |= GrafixAttribute_StickyBuffer;

    g_lock.Lock();

    CGrafixSurface* surface = NULL;
    ECode ec;

    if (!m_stack) {
        GFX_ERROR_MESSAGE("The grafixobjectstack doesn't initialized\n");
        ec = E_STACK_NOT_INITIALIZE;

        goto Exit;
    }

    surface = new CGrafixSurface;
    if (!surface) {
        GFX_EXPRESSION(ERRORLEVEL,
            kprintf("Create the surface that be not enough memory\n"));
        ec = E_OUT_OF_MEMORY;

        goto Exit;
    }

    surface->AddRef();

    if (PixelFormat_ARGB == surfaceParams.pixelFormat) {
        surfaceParams.attribute |= GrafixAttribute_AlphaPixel;
    }

    if (PixelFormat_Unknown == surfaceParams.pixelFormat) {
        if (surfaceParams.attribute & GrafixAttribute_AlphaPixel) {
            surfaceParams.pixelFormat = PixelFormat_ARGB;
        }
        else {
            surfaceParams.pixelFormat = PixelFormat_RGB24;
        }
    }

    assert(surfaceParams.pixelFormat == PixelFormat_ARGB
        || !(surfaceParams.attribute & GrafixAttribute_AlphaPixel));

    ec = g_bufferCache.GetBufferNode(
                    surface,
                    surfaceParams.surfaceArea.width,
                    surfaceParams.surfaceArea.height,
                    surfaceParams.attribute,
                    surfaceParams.pixelFormat,
                    &surface->m_cacheBuffer);
    if (GFX_FAILED(ec)) {
        goto Exit;
    }

    ec = surface->Initialize(surfaceParams, NULL);
    if (GFX_FAILED(ec)) {
        goto Exit;
    }

    *ppGrafixSurface = IGrafixSurface::Probe((_IInterface*)surface);
    if (*ppGrafixSurface) {
        (*ppGrafixSurface)->AddRef();
        ec = NOERROR;
    }
    else {
        ec = E_NO_INTERFACE;
    }

Exit:
    g_lock.Unlock();

    if (surface) {
        surface->Release();
    }

    return ec;
}

ECode CGrafixEngine::BroadcastGrafixEvent(
    /* [in] */ const struct GrafixObjectEvent & event)
{
    ECode ec = NOERROR;

    g_lock.Lock();

    Int32 index;
    Void* element;

    if (!m_stack) {
        GFX_ERROR_MESSAGE("The grafixobjectstack doesn't initialized\n");
        ec = E_STACK_NOT_INITIALIZE;

        goto Exit;
    }

    fusion_vector_foreach(element, index, m_stack->m_windows)
    {
        CGrafixObject* object = (CGrafixObject *)element;
#ifdef _DEBUG
        ec =
#endif
        object->AddEvent(event);
#ifdef _DEBUG
        if (FAILED(ec)) {
            GFX_EXPRESSION(ERRORLEVEL,
                kprintf("BoardEvent (type: 0x%08x) to grafixobject %S failed\n",
                event.type, object->m_title));
        }
#endif
    }

Exit:
    g_lock.Unlock();

    return ec;
}

ECode CGrafixEngine::GetAllGrafixObjects(
    /* [in] */ Int32 appletId,
    /* [out] */ IObjectEnumerator ** ppGrafixObjects)
{
    ECode   ec = NOERROR;

    if (!ppGrafixObjects) {
        GFX_EXPRESSION(ERRORLEVEL,
            kprintf("Invalid argument\n"));

        return E_INVALID_ARGUMENT;
    }

    *ppGrafixObjects = NULL;

    CGrafixObjectEnumerator* enumerator = NULL;

    g_lock.Lock();

    if (!m_stack) {
        GFX_ERROR_MESSAGE("The grafixobjectstack doesn't initialized\n");
        ec = E_STACK_NOT_INITIALIZE;

        goto Exit;
    }

    enumerator = new CGrafixObjectEnumerator;
    if (!enumerator) {
        GFX_EXPRESSION(ERRORLEVEL,
            kprintf("Not enough memory\n"));
        ec = E_OUT_OF_MEMORY;

        goto Exit;
    }

    enumerator->AddRef();

    ec = enumerator->Initialize(appletId, this);
    if (GFX_FAILED(ec)) {
        goto Exit;
    }

    *ppGrafixObjects = IObjectEnumerator::Probe(enumerator);
    if (*ppGrafixObjects) {
        (*ppGrafixObjects)->AddRef();
        ec = NOERROR;
    }
    else {
        ec = E_NO_INTERFACE;
    }

Exit:
    g_lock.Unlock();

    if (enumerator) {
        enumerator->Release();
    }

    return ec;
}

ECode CGrafixEngine::AcquireGrafixEventBuffer(
    /* [in] */ Int32 contextId,
    /* [out] */ IGrafixEventBuffer ** ppEventBuffer)
{
    if (!ppEventBuffer) {
        GFX_ERROR_MESSAGE("Invalid argument\n");

        return E_INVALID_ARGUMENT;
    }

    *ppEventBuffer = NULL;

    ECode ec = NOERROR;

    g_lock.Lock();

    if (!m_stack) {
        GFX_ERROR_MESSAGE("The grafixobjectstack doesn't initialized\n");
        ec = E_STACK_NOT_INITIALIZE;

        goto Exit;
    }

    CGrafixEventBuffer* buffer;

#ifdef _zener
    GFX_EXPRESSION(TRIPLEVEL, {
        Int32 id;

        ec = ::GetCurrentProcess()->GetId(&id);
        if (GFX_FAILED(ec)) {
            goto Exit;
        }

        assert(id == contextId);
    });
#endif

    ec = m_stack->AcquireEventBuffer(contextId, &buffer);
    if (GFX_FAILED(ec)) {
        goto Exit;
    }

    *ppEventBuffer = IGrafixEventBuffer::Probe(buffer);
    if (!(*ppEventBuffer)) {
        buffer->Release();
        ec = E_NO_INTERFACE;
    }

Exit:
    g_lock.Unlock();

    return ec;
}

ECode CGrafixEngine::Render()
{
    g_lock.Lock();

    if (m_currentLayer) {
        m_currentLayer->Render();
    }

    g_lock.Unlock();

    return NOERROR;
}

ECode CGrafixEngine::RenderRegion(
    /* [in] */ Int32 x,
    /* [in] */ Int32 y,
    /* [in] */ Int32 width,
    /* [in] */ Int32 height)
{
    if (width <= 0 || height <= 0) {
        GFX_ERROR_MESSAGE("Invalid argument\n");

        return E_INVALID_ARGUMENT;
    }

    g_lock.Lock();

    if (m_currentLayer) {
        m_currentLayer->RenderRegion(x, y, width, height);
    }

    g_lock.Unlock();

    return NOERROR;
}

ECode CGrafixEngine::SetScreenScreenOrientation(
    /* [in] */ ScreenOrientation orientation)
{
    ECode ec = NOERROR;

    if (orientation < ScreenOrientation_Up || orientation > ScreenOrientation_Right) {
        GFX_ERROR_MESSAGE("Invalid argument\n");

        return E_INVALID_ARGUMENT;
    }

    g_lock.Lock();

    if (!m_stack) {
        GFX_ERROR_MESSAGE("The grafixobjectstack doesn't initialized\n");
        ec = E_STACK_NOT_INITIALIZE;

        goto Exit;
    }

    m_orientation = orientation;
    m_stack->SetOrientation(orientation);

Exit:
    g_lock.Unlock();

    return ec;
}

ECode CGrafixEngine::GetScreenScreenOrientation(
    /* [out] */ ScreenOrientation * pOrientation)
{
    if (!pOrientation) {
        GFX_ERROR_MESSAGE("Invalid argument\n");

        return E_INVALID_ARGUMENT;
    }

    ECode ec = NOERROR;

    g_lock.Lock();

    if (!m_stack) {
        GFX_ERROR_MESSAGE("The grafixobjectstack doesn't initialized\n");
        ec = E_STACK_NOT_INITIALIZE;

        goto Exit;
    }

    *pOrientation = m_orientation;

Exit:
    g_lock.Unlock();

    return ec;
}

Void CGrafixEngine::Render(
    /* [in] */ CGrafixSurface * pSurface)
{
    assert(pSurface);

    g_lock.Lock();

    if (m_currentLayer) {
        RectangleLink* node;


        ForEachDLinkNode(RectangleLink*, node, &m_renderRect) {
            if (!node->m_free) {
                Rectangle updateRect = node->m_rect;

#ifndef _neptune
                m_currentLayer->Render2Framebuffer(
                                pSurface, &updateRect, m_orientation);
#endif
                RectangleLink* listNode;
                ForEachDLinkNode(RectangleLink*,
                                listNode,
                                &m_layerTransparentRect) {
                    if (!listNode->m_free) {
                        Rectangle transparentRect = listNode->m_rect;
                        if (RectangleIntersect(&transparentRect, &updateRect)) {
                            m_currentLayer->TranparentLayer(&transparentRect,
                                                            m_orientation);
                        }
                    }
                }
            }
        }

#if defined(_win32) || defined(_neptune) || defined(_wince)
        ForEachDLinkNode(RectangleLink*, node, &m_renderRect) {
            if (!node->m_free) {
                m_currentLayer->RenderRegion(
                    RECTANGLE_VALS(&node->m_rect), pSurface);
            }
        }
#endif
    }

    m_renderRect.RemoveAll();

    m_layerTransparentRect.RemoveAll();

    g_lock.Unlock();
}

ECode CGrafixEngine::EnableStack(
    /* [in] */ Int32 width,
    /* [in] */ Int32 height)
{
    ECode ec = NOERROR;

    g_lock.Lock();

    if (!m_stack) {
        m_stack = new GrafixObjectStack;
        if (!m_stack) {
            GFX_EXPRESSION(ERRORLEVEL,
                kprintf("Create window stack that is not enough memory\n"));
            ec = E_OUT_OF_MEMORY;

            goto Exit;
        }

        ec = m_stack->Initialize(this, width, height);
        if (GFX_FAILED(ec)) {
            goto Exit;
        }

        m_stack->SetOrientation(m_orientation);

        g_transformMatrix[ScreenOrientation_Left].y0 = width - 1;
        g_transformMatrix[ScreenOrientation_Down].x0 = width - 1;
        g_transformMatrix[ScreenOrientation_Down].y0 = height - 1;
        g_transformMatrix[ScreenOrientation_Right].x0 = height - 1;

        g_RenderMatrix[ScreenOrientation_Left].x0 = width - 1;
        g_RenderMatrix[ScreenOrientation_Down].x0 = width - 1;
        g_RenderMatrix[ScreenOrientation_Down].y0 = height - 1;
        g_RenderMatrix[ScreenOrientation_Right].y0 = height - 1;

        g_screenSize = AlignPageUp(width * height * 4);
    }

    m_stackRef++;

Exit:
    g_lock.Unlock();

    return ec;
}

ECode CGrafixEngine::DisableStack()
{
    g_lock.Lock();

    m_stackRef--;

    if (0 == m_stackRef) {
        delete m_stack;
        m_stack = NULL;
    }

    g_lock.Unlock();

    return NOERROR;
}
